package com.bjy.qa.util.fileparser;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.bjy.qa.entity.functionaltest.Api;
import com.bjy.qa.entity.functionaltest.ApiParam;
import com.bjy.qa.enumtype.CatalogType;
import com.bjy.qa.enumtype.ParamKind;
import com.bjy.qa.exception.MyException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.saasquatch.jsonschemainferrer.DefaultPolicies;
import com.saasquatch.jsonschemainferrer.JsonSchemaInferrer;
import com.saasquatch.jsonschemainferrer.SpecVersion;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.apache.commons.lang.StringUtils;

import java.util.*;

/**
 *  openApi 格式文件解析器，将 openApi 格式文件解析为 Api 对象
 */
public class OpenApiParser {
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final JsonSchemaInferrer inferrer = JsonSchemaInferrer.newBuilder()
            .setSpecVersion(SpecVersion.DRAFT_04)
//            .addFormatInferrers(FormatInferrers.email(), FormatInferrers.ip()) // 校验器（格式推断器：邮件地址、ip 地址）
//            .setAdditionalPropertiesPolicy(AdditionalPropertiesPolicies.notAllowed()) // 设置附加属性策略（AdditionalPropertiesPolicies） 。notAllowed：不允许附加属性
//            .setRequiredPolicy(RequiredPolicies.nonNullCommonFields()) // 设置必填项（Required） 。nonNullCommonFields：非空字段都会被设置为必填项
//            .addEnumExtractors(EnumExtractors.validEnum(java.time.Month.class), EnumExtractors.validEnum(java.time.DayOfWeek.class)) // 枚举提取器
            .setDefaultPolicy(DefaultPolicies.useFirstSamples())
            .build();

    /**
     * 将 openApi 格式文件解析为 Api 对象
     * @param filePath openApi 格式文件路径
     * @param projectId 项目 id
     * @return
     */
    public Map<String, List<Api>> toApi(String filePath, Long projectId) {
        Map<String, List<Api>> apis = new HashMap<>();
        try {

            ParseOptions options = new ParseOptions();
            options.setResolve(true);
            options.setResolveFully(true); // 加载 $ref 引用内容

            // 得到 OpenAPI 对象
            SwaggerParseResult swaggerParseResult = new OpenAPIParser().readLocation(filePath, null, options);
            OpenAPI openAPI = swaggerParseResult.getOpenAPI();
            if (openAPI == null) {
                throw new MyException("非 OpenAPI 文件格式！");
            }

            String host = parserHost(openAPI.getServers()); // 获取 api host
            String title = openAPI.getInfo().getTitle(); // 获取 api title

            // 遍历 paths，解析 api
            Paths paths = openAPI.getPaths();
            for (Map.Entry<String, PathItem> entry : paths.entrySet()) {
                parserGet(projectId, title, host, entry.getKey(), entry.getValue(), apis); // 解析 get 请求
                parserPost(projectId, title, host, entry.getKey(), entry.getValue(), apis); // 解析 post 请求
                parserPut(projectId, title, host, entry.getKey(), entry.getValue(), apis); // 解析 put 请求
                parserDelete(projectId, title, host, entry.getKey(), entry.getValue(), apis); // 解析 delete 请求
                parserPatch(projectId, title, host, entry.getKey(), entry.getValue(), apis); // 解析 patch 请求
            }
        } catch (Exception e) {
            throw new MyException("导入接口定义 - OpenAPI 格式失败：解析 OpenAPI 文件失败。" + e.getMessage());
        }
        return apis;
    }


    /**
     * 获取 host
     * @param servers 服务器列表
     * @return
     */
    private String parserHost(List<Server> servers) {
        if (servers.size() > 0){
            return servers.get(0).getUrl();
        } else {
            return "";
        }
    }

    /**
     * 解析 catalog 名称（也就是 swaggerApi 的 tags）
     * @param title openApi title（如果 swaggerApi 没有 tags 则返回此字段）
     * @param swaggerApi OpenAPI（swagger）文档中定义的 api
     * @return
     */
    private String parserCatalog(String title, Operation swaggerApi) {
        List<String> tags = swaggerApi.getTags();
        if (tags != null && tags.size() > 0) {
            return tags.get(0);
        }
        return title;
    }

    /**
     * 解析 api 的基础 信息（name、host、uri、projectId），并返回 api 对象
     * @param projectId 项目id
     * @param host host
     * @param uri uri
     * @param swaggerApi OpenAPI（swagger）文档中定义的 api
     * @return
     */
    private Api parserBase(Long projectId, String host, String uri, Operation swaggerApi) {
        Api api = new Api();
        api.setProjectId(projectId);
        api.setHost(host);
        api.setUri(uri);
        api.setType(CatalogType.INTERFACE_HTTP.getValue());
        api.setApiParams(new ArrayList<>());

        // 设置 api 名称
        String description = swaggerApi.getDescription();
        if (StringUtils.isEmpty(description)) {
            description = swaggerApi.getSummary();
        }
        api.setName(description);

        // String operationId = swaggerApi.getOperationId(); // api Id（暂时不需要，注释）
        return api;
    }

    /**
     * 解析 content 对象（apiParam 的 schema、example）
     * @param content content 对象
     * @param apiParam apiParam 对象
     * @return 返回 content 的 contentType
     */
    private String parserContent(Content content, ApiParam apiParam) {
        String contentType = "FORM"; // 默认为 form

        for (String key : content.keySet()) {
            if ("application/json".equals(key.toLowerCase())) { // content 是 json 类型
                contentType = "JSON"; // json 的改为 json

                // 设置 schema
                io.swagger.v3.oas.models.media.Schema schema = content.get(key).getSchema();
                if (schema != null) {
                    recursiveRepairSchema(schema); // 递归补齐 schema 缺少的属性
                    apiParam.setDes(JSON.toJSONString(schema, SerializerFeature.DisableCircularReferenceDetect));
                }

                /* 由于保存 api 时会自动生成 example，所以这里不需要再设置 example
                // 设置 example
                Map<String, Example> examples = content.get(key).getExamples();
                if (examples != null) {
                    for (Map.Entry<String, Example> en : examples.entrySet()) {
                        Example e = en.getValue();
                        if (e != null) {
                            apiParam.setExample(e.getValue().toString());
                        }
                    }
                }*/
            } else if ("application/x-www-form-urlencoded".equals(key.toLowerCase()) || "multipart/form-data".equals(key.toLowerCase())) { // content 是 x-www-form-urlencoded 类型
                io.swagger.v3.oas.models.media.Schema schema = content.get(key).getSchema();
                if (schema != null) {
                    recursiveRepairSchema(schema); // 递归补齐 schema 缺少的属性
                    apiParam.setDes(JSON.toJSONString(schema, SerializerFeature.DisableCircularReferenceDetect));
                }
                // 同样 由于保存 api 时会自动生成 example，所以这里不需要再设置 example
            } else {
                throw new MyException("parserContent error - 不识别的 key:" + key);
            }
        }

        return contentType;
    }

    /**
     * 递归补齐 schema 缺少的属性
     * @param schema schema 对象
     */
    private void recursiveRepairSchema(io.swagger.v3.oas.models.media.Schema schema) {
        if (schema != null) {
            // 设置 type
            if (schema.getType() == null) {
                if (schema.getTypes() != null) {
                    for (String obj : (Set<String>) schema.getTypes()) {
                        schema.setType(obj);
                        break;
                    }
                }
            }
            // 设置 example
            if (schema.getDefault() == null && schema.getExample() != null) {
                schema.setDefault(schema.getExample());
            }
            // 如果有 Properties 则递归补齐 schema 缺少的属性
            if (schema.getProperties() != null) {
                for (Map.Entry<String, io.swagger.v3.oas.models.media.Schema> entry : ((Map<String, io.swagger.v3.oas.models.media.Schema>) schema.getProperties()).entrySet()) {
                    recursiveRepairSchema(entry.getValue());
                }
            }
        }
    }

    /**
     * 解析 body 参数
     * @param requestBody body 对象
     * @param api api 对象
     * @return
     */
    private ApiParam parserBody(RequestBody requestBody, Api api) {
        ApiParam apiParam = new ApiParam();
        apiParam.setKind(ParamKind.KIND_BODY.getValue());
        apiParam.setName(ParamKind.KIND_BODY.getName());
        apiParam.setDes("{\"type\":\"object\",\"properties\":{}}");
        apiParam.setExample("{}");

        api.setExtra1("FORM"); // 默认设置为 form 类型，后边遇到 json 类型会覆盖

        if (requestBody != null) {
            String contentType = parserContent(requestBody.getContent(), apiParam);
            api.setExtra1(contentType); // 设置 api 的 contentType
        }

        return apiParam;
    }

    /**
     * 解析 responses(assert) 参数
     * @param responses ApiResponses
     * @return
     */
    private ApiParam parserResponses(ApiResponses responses) {
        ApiParam apiParam = new ApiParam();
        apiParam.setKind(ParamKind.KIND_ASSERT.getValue());
        apiParam.setName(ParamKind.KIND_ASSERT.getName());
        apiParam.setDes("{\"type\":\"object\",\"properties\":{}}");
        apiParam.setExample("{}");

        for (String key : responses.keySet()) { // key 为状态码（200、201、400等），所以随便取一个就行
            parserContent(responses.get(key).getContent(), apiParam);
            break;
        }
        return apiParam;
    }

    /**
     * 解析 header 参数
     * @param swaggerApi OpenAPI（swagger）文档中定义的 api
     * @return
     */
    private ApiParam parserHead(Operation swaggerApi) {
        com.alibaba.fastjson.JSONObject schema = JSON.parseObject("{\"type\":\"object\",\"properties\":{}}");
        Map<String, Map<String, String>> properties = new HashMap<>();
        List<String> required = new ArrayList<>();

        for (Parameter parameter : swaggerApi.getParameters()) {
            if ("header".equals(parameter.getIn().toLowerCase())) {
                if (parameter.getRequired()) {
                    required.add(parameter.getName());
                }

                Map<String, String> propertie = new HashMap<>();

                String type = parameter.getSchema().getType();
                if (type == null) {
                    if (parameter.getSchema().getTypes() != null) {
                        for (String obj : (Set<String>) parameter.getSchema().getTypes()) {
                            type = obj;
                            break;
                        }
                    }
                }
                propertie.put("type", type);

                if (parameter.getExample() != null) {
                    propertie.put("default", parameter.getExample().toString());
                }
                if (parameter.getDescription() != null) {
                    propertie.put("description", parameter.getDescription());
                }
                properties.put(parameter.getName(), propertie);
            }
        }

        if (properties.size() != 0) {
            schema.getObject("properties", Map.class).putAll(properties);
        }
        if (required.size() != 0) {
            schema.put("required", required);
        }

        ApiParam apiParam = new ApiParam();
        apiParam.setKind(ParamKind.KIND_HEADER.getValue());
        apiParam.setName(ParamKind.KIND_HEADER.getName());
        apiParam.setDes(JSON.toJSONString(schema, SerializerFeature.DisableCircularReferenceDetect));
        apiParam.setExample("{}");

        return apiParam;
    }

    /**
     * 解析 cookie 参数
     * @param swaggerApi OpenAPI（swagger）文档中定义的 api
     * @return
     */
    private ApiParam parserCookie(Operation swaggerApi) {
        com.alibaba.fastjson.JSONObject schema = JSON.parseObject("{\"type\":\"object\",\"properties\":{}}");
        Map<String, Map<String, String>> properties = new HashMap<>();
        List<String> required = new ArrayList<>();

        for (Parameter parameter : swaggerApi.getParameters()) {
            if ("cookie".equals(parameter.getIn().toLowerCase())) {
                if (parameter.getRequired()) {
                    required.add(parameter.getName());
                }

                Map<String, String> propertie = new HashMap<>();

                String type = parameter.getSchema().getType();
                if (type == null) {
                    if (parameter.getSchema().getTypes() != null) {
                        for (String obj : (Set<String>) parameter.getSchema().getTypes()) {
                            type = obj;
                            break;
                        }
                    }
                }
                propertie.put("type", type);

                if (parameter.getExample() != null) {
                    propertie.put("default", parameter.getExample().toString());
                }
                if (parameter.getDescription() != null) {
                    propertie.put("description", parameter.getDescription());
                }
                properties.put(parameter.getName(), propertie);
            }
        }

        if (properties.size() != 0) {
            schema.getObject("properties", Map.class).putAll(properties);
        }
        if (required.size() != 0) {
            schema.put("required", required);
        }

        ApiParam apiParam = new ApiParam();
        apiParam.setKind(ParamKind.KIND_COOKIE.getValue());
        apiParam.setName(ParamKind.KIND_COOKIE.getName());
        apiParam.setDes(JSON.toJSONString(schema, SerializerFeature.DisableCircularReferenceDetect));
        apiParam.setExample("{}");

        return apiParam;
    }

    /**
     * 解析 query（url param） 参数
     * @param swaggerApi OpenAPI（swagger）文档中定义的 api
     * @return
     */
    private ApiParam parserParams(Operation swaggerApi) {
        com.alibaba.fastjson.JSONObject schema = JSON.parseObject("{\"type\":\"object\",\"properties\":{}}");
        Map<String, Map<String, String>> properties = new HashMap<>();
        List<String> required = new ArrayList<>();

        for (Parameter parameter : swaggerApi.getParameters()) {
            if ("query".equals(parameter.getIn().toLowerCase())) {
                if (parameter.getRequired()) {
                    required.add(parameter.getName());
                }

                Map<String, String> propertie = new HashMap<>();

                String type = parameter.getSchema().getType();
                if (type == null) {
                    if (parameter.getSchema().getTypes() != null) {
                        for (String obj : (Set<String>) parameter.getSchema().getTypes()) {
                            type = obj;
                            break;
                        }
                    }
                }
                propertie.put("type", type);

                if (parameter.getExample() != null) {
                    propertie.put("default", parameter.getExample().toString());
                }
                if (parameter.getDescription() != null) {
                    propertie.put("description", parameter.getDescription());
                }
                properties.put(parameter.getName(), propertie);
            }
        }

        if (properties.size() != 0) {
            schema.getObject("properties", Map.class).putAll(properties);
        }
        if (required.size() != 0) {
            schema.put("required", required);
        }

        ApiParam apiParam = new ApiParam();
        apiParam.setKind(ParamKind.KIND_URL_PARAM.getValue());
        apiParam.setName(ParamKind.KIND_URL_PARAM.getName());
        apiParam.setDes(JSON.toJSONString(schema, SerializerFeature.DisableCircularReferenceDetect));
        apiParam.setExample("{}");

        return apiParam;
    }

    /**
     * 解析 get 请求
     * @param projectId 项目id
     * @param catalogName catalog 名称
     * @param host host
     * @param uri uri
     * @param pathItem pathItem
     * @param apis api 列表
     */
    private void parserGet(Long projectId, String catalogName, String host, String uri, PathItem pathItem, Map<String, List<Api>> apis) {
        Operation swaggerApi = pathItem.getGet();
        if (swaggerApi != null) {
            Api api = parserBase(projectId, host, uri, swaggerApi);
            api.setMethod("GET");

            api.getApiParams().add(parserHead(swaggerApi)); // 解析 head 参数
            api.getApiParams().add(parserParams(swaggerApi)); // 解析 query（url param） 参数
            api.getApiParams().add(parserBody(swaggerApi.getRequestBody(), api)); // 解析 body 参数
            api.getApiParams().add(parserCookie(swaggerApi)); // 解析 cookie 参数
            api.getApiParams().add(parserResponses(swaggerApi.getResponses())); // 解析 responses(assert) 参数

            // 将解析出来的 api 添加到 api map 中
            String apiKey = parserCatalog(catalogName, swaggerApi); // 得到 catalog 名称用作 api 的 key
            List<Api> apiList = apis.get(apiKey);
            if (apiList == null) {
                apiList = new ArrayList<>();
                apis.put(apiKey, apiList);
            }
            apiList.add(api);
        }
    }

    /**
     * 解析 post 请求
     * @param projectId 项目id
     * @param catalogName catalog 名称
     * @param host host
     * @param uri uri
     * @param pathItem pathItem
     * @param apis api 列表
     */
    private void parserPost(Long projectId, String catalogName, String host, String uri, PathItem pathItem, Map<String, List<Api>> apis) {
        Operation swaggerApi = pathItem.getPost();
        if (swaggerApi != null) {
            Api api = parserBase(projectId, host, uri, swaggerApi); // 生成 api
            api.setMethod("POST");

            api.getApiParams().add(parserHead(swaggerApi)); // 解析 head 参数
            api.getApiParams().add(parserParams(swaggerApi)); // 解析 query（url param） 参数
            api.getApiParams().add(parserBody(swaggerApi.getRequestBody(), api)); // 解析 body 参数
            api.getApiParams().add(parserCookie(swaggerApi)); // 解析 cookie 参数
            api.getApiParams().add(parserResponses(swaggerApi.getResponses())); // 解析 responses(assert) 参数

            // 将解析出来的 api 添加到 api map 中
            String apiKey = parserCatalog(catalogName, swaggerApi); // 得到 catalog 名称用作 api 的 key
            List<Api> apiList = apis.get(apiKey);
            if (apiList == null) {
                apiList = new ArrayList<>();
                apis.put(apiKey, apiList);
            }
            apiList.add(api);
        }
    }

    /**
     * 解析 put 请求
     * @param projectId 项目id
     * @param catalogName catalog 名称
     * @param host host
     * @param uri uri
     * @param pathItem pathItem
     * @param apis api 列表
     */
    private void parserPut(Long projectId, String catalogName, String host, String uri, PathItem pathItem, Map<String, List<Api>> apis) {
        Operation swaggerApi = pathItem.getPut();
        if (swaggerApi != null) {
            Api api = parserBase(projectId, host, uri, swaggerApi);
            api.setMethod("PUT");

            api.getApiParams().add(parserHead(swaggerApi)); // 解析 head 参数
            api.getApiParams().add(parserParams(swaggerApi)); // 解析 query（url param） 参数
            api.getApiParams().add(parserBody(swaggerApi.getRequestBody(), api)); // 解析 body 参数
            api.getApiParams().add(parserCookie(swaggerApi)); // 解析 cookie 参数
            api.getApiParams().add(parserResponses(swaggerApi.getResponses())); // 解析 responses(assert) 参数

            // 将解析出来的 api 添加到 api map 中
            String apiKey = parserCatalog(catalogName, swaggerApi); // 得到 catalog 名称用作 api 的 key
            List<Api> apiList = apis.get(apiKey);
            if (apiList == null) {
                apiList = new ArrayList<>();
                apis.put(apiKey, apiList);
            }
            apiList.add(api);
        }
    }

    /**
     * 解析 delete 请求
     * @param projectId 项目id
     * @param catalogName catalog 名称
     * @param host host
     * @param uri uri
     * @param pathItem pathItem
     * @param apis api 列表
     */
    private void parserDelete(Long projectId, String catalogName, String host, String uri, PathItem pathItem, Map<String, List<Api>> apis) {
        Operation swaggerApi = pathItem.getDelete();
        if (swaggerApi != null) {
            Api api = parserBase(projectId, host, uri, swaggerApi);
            api.setMethod("DELETE");

            api.getApiParams().add(parserHead(swaggerApi)); // 解析 head 参数
            api.getApiParams().add(parserParams(swaggerApi)); // 解析 query（url param） 参数
            api.getApiParams().add(parserBody(swaggerApi.getRequestBody(), api)); // 解析 body 参数
            api.getApiParams().add(parserCookie(swaggerApi)); // 解析 cookie 参数
            api.getApiParams().add(parserResponses(swaggerApi.getResponses())); // 解析 responses(assert) 参数

            // 将解析出来的 api 添加到 api map 中
            String apiKey = parserCatalog(catalogName, swaggerApi); // 得到 catalog 名称用作 api 的 key
            List<Api> apiList = apis.get(apiKey);
            if (apiList == null) {
                apiList = new ArrayList<>();
                apis.put(apiKey, apiList);
            }
            apiList.add(api);
        }
    }

    /**
     * 解析 patch 请求
     * @param projectId 项目id
     * @param catalogName catalog 名称
     * @param uri uri
     * @param pathItem pathItem
     * @param apis api 列表
     */
    private void parserPatch(Long projectId, String catalogName, String host, String uri, PathItem pathItem, Map<String, List<Api>> apis) {
        Operation swaggerApi = pathItem.getPatch();
        if (swaggerApi != null) {
            Api api = parserBase(projectId, host, uri, swaggerApi);
            api.setMethod("PATCH");

            api.getApiParams().add(parserHead(swaggerApi)); // 解析 head 参数
            api.getApiParams().add(parserParams(swaggerApi)); // 解析 query（url param） 参数
            api.getApiParams().add(parserBody(swaggerApi.getRequestBody(), api)); // 解析 body 参数
            api.getApiParams().add(parserCookie(swaggerApi)); // 解析 cookie 参数
            api.getApiParams().add(parserResponses(swaggerApi.getResponses())); // 解析 responses(assert) 参数

            // 将解析出来的 api 添加到 api map 中
            String apiKey = parserCatalog(catalogName, swaggerApi); // 得到 catalog 名称用作 api 的 key
            List<Api> apiList = apis.get(apiKey);
            if (apiList == null) {
                apiList = new ArrayList<>();
                apis.put(apiKey, apiList);
            }
            apiList.add(api);
        }
    }
}
